﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using MetaNote.Model;
using MacroDictionary = System.Collections.Generic.Dictionary<System.Guid, MetaNote.Model.Macro>;

namespace MetaNote
{
    public static class Common
    {
        private const string settingsFileName = "settings.xml";
        private static Settings m_Settings = null;

        private static void Log(string message)
        {
            RaiseEvent(ApplicationEvents.Logging, message);
            //TODO: log message. (this would be something a plugin does)
            Debug.WriteLine(message);
            RaiseEvent(ApplicationEvents.Logged, message);
        }

        private static bool RaiseEvent(ApplicationEvents applicationEvents, object e)
        {
            //TODO: raise application event
            return true;
        }

        private static void Log(string message, Exception ex)
        {
            Log(message);
            Log(ex);
        }

        private static void Log(Exception ex)
        {
            Log(ex.Message);
            Log(ex.ToString());            
            if (ex.InnerException != null)
            {
                Log("Inner exception:");
                Log(ex.InnerException);
            }
        }

        internal static Settings Settings
        {
            get
            {
                if (m_Settings == null)
                {
                    m_Settings = new Settings();
                    var settingsPathAndFile = Path.Combine(m_Settings.UserFilePath, settingsFileName);
                    if (File.Exists(settingsPathAndFile))
                    {
                        try
                        {
                            m_Settings = (Settings)LoadObjectFromXMLFile(settingsPathAndFile, typeof(Settings));
                        }
                        catch (Exception ex)
                        {
                            Log("Horrid failure loading settings.", ex);
                            //And bury it there ;-)
                            //Default settings will be returned.
                            //(a possible idea is to archive the failed settings into a different filename,
                            // such as settings.xml.broken.yyyyMMddHHmmss...)
                        }
                    }
                }
                return m_Settings;
            }
        }

        internal static void SaveSettings()
        {
            RaiseEvent(ApplicationEvents.SavingSettings, m_Settings);

            EnsurePathExists(Common.Settings.UserFilePath);
            var settingsFileAndPath = Path.Combine(Common.Settings.UserFilePath, settingsFileName);
            SaveObjectToXmlFile(settingsFileAndPath, m_Settings);

            RaiseEvent(ApplicationEvents.SavedSettings, m_Settings);
        }

        internal static void SaveMacros(MacroDictionary macroDictionary)
        {
            Macro[] macros = macroDictionary.Values.ToArray();

            EnsurePathExists(Common.Settings.UserFilePath);
            var macroPathAndFile = Path.Combine(Common.Settings.UserFilePath, Common.Settings.MacroFileName);
            SaveObjectToXmlFile(macroPathAndFile, macros);
        }

        private static void EnsurePathExists(string userPath)
        {
            if (!Directory.Exists(userPath))
            {
                Directory.CreateDirectory(userPath);
            }
        }

        internal static MacroDictionary  LoadMacros()
        {
            var macroPathAndFile = Path.Combine(Common.Settings.UserFilePath, Common.Settings.MacroFileName);
            Macro[] macros;
            if (File.Exists(macroPathAndFile))
            {
                macros = (Macro[])LoadObjectFromXMLFile(macroPathAndFile, typeof(Macro[]));
            }
            else
            {
                macros = new Macro[0];
            }

            if (macros == null)
            {
                macros = new Macro[0];
            }

            var macroDictionary = new MacroDictionary();

            int i = 0;
            foreach (var macro in macros)
            {
                //macro.Key = Guid.NewGuid();
                macroDictionary.Add(macro.Key, macro);
                i++;
            }
            return macroDictionary;
        }

        private static object LoadObjectFromXMLFile(string xmlFileName, Type type)
        {
            if (!File.Exists(xmlFileName))
            {
                throw new FileNotFoundException("File Not Found, hence can't deserialize into " + type.ToString(), xmlFileName);
            }

            XmlSerializer xsZing = new XmlSerializer(type);

            using (FileStream fsStream = new FileStream(xmlFileName, FileMode.Open))
            using (XmlTextReader xrReader = new XmlTextReader(fsStream))
            {
                try
                {
                    return xsZing.Deserialize(xrReader);
                }
                catch (Exception ex)
                {
                    //Archive the failed file, so that it's not 
                    //permanently over written upon save
                    Archive(xmlFileName, "failed");

                    Log("Error loading file '" + xmlFileName + "', details:" + ex.ToString());
                    //throw;
                    return null;
                }
            }

        }

        private static void Archive(string xmlFileName, string reason)
        {
            string archiveFileName = Path.Combine(Path.GetDirectoryName(xmlFileName), reason + "_" + DateTime.Now.ToString("yyyyMMddHHmmss") + "_" + Path.GetFileName(xmlFileName));
            File.Copy(xmlFileName, archiveFileName);
        }

        private static void SaveObjectToXmlFile(string fileName, object obj)
        {
            try
            {
                var xsZing = new XmlSerializer(obj.GetType());
                using (var twWriter = new StreamWriter(fileName))
                {
                    xsZing.Serialize(twWriter, obj);
                }

            }
            catch (Exception ex)
            {
                Log("Error while saving file to object. file:'" + fileName + "'", ex);
                throw;
            }
        }

        public static void StartDefaultWebBrowser(string url)
        {
            try
            {
                //TODO: Set a status message before trying to connect, and then 
                //revert to the previous message afterward. (perhaps have a 'using' statement to achieve this)
                ProcessStartInfo psi = new ProcessStartInfo();
                psi.UseShellExecute = true;
                psi.FileName = url;
                Process.Start(psi);
            }
            catch (Win32Exception)
            {
                //TODO: Use an array of common browser names, sorted by popularity amongst metanote users.
                // chrome.exe, firefox.exe, iexplore.exe, opera.exe, safari.exe.
                // In some cases, there isn't any program registered for the http:// protocol.
                // In that case we need to start a browser by a more brute-force way
                try
                {
                    // Try Internet explorer
                    Process.Start("iexplore.exe", url);
                }
                catch (Exception)
                {
                    try
                    {
                        // as a last resort - try firefox
                        Process.Start("firefox.exe", url);
                    }
                    catch (Exception)
                    {
                        MessageBox.Show("Unable to start any web browser on your computer.  You must manually navigate to:\r\n " +  url, "MetaNote", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    }
                }
            }
        }

        internal static void ShowTodo(string featureName)
        {
            if (!RaiseEvent(ApplicationEvents.ShowingTodo, featureName)) return;
            var f = new NeedsYouForm(featureName);
            f.ShowDialog();            
            RaiseEvent(ApplicationEvents.ShowedTodo, featureName);
        }

        internal static void SaveRecentFiles(HashSet<string> recentFiles)
        {
            EnsurePathExists(Common.Settings.UserFilePath);
            var recentFilesFileAndPath = Path.Combine(Common.Settings.UserFilePath, Common.Settings.RecentFilesFileName);
            SaveObjectToXmlFile(recentFilesFileAndPath, recentFiles);            
        }

        internal static HashSet<string> LoadRecentFiles()
        {
            var recentFilesPathAndFile = Path.Combine(Common.Settings.UserFilePath, Common.Settings.RecentFilesFileName);
            HashSet<string> recentFiles;
            if (File.Exists(recentFilesPathAndFile))
            {
                recentFiles = (HashSet<string>)LoadObjectFromXMLFile(recentFilesPathAndFile, typeof(HashSet<string>));
            }
            else
            {
                recentFiles = new HashSet<string>();
            }

            return recentFiles;            
        }

        /// <summary>
        /// Loads a menu.
        /// </summary>
        /// <param name="toolStrip">The tool strip.</param>
        /// <param name="macros">The macros.</param>
        internal static void LoadMenu(ToolStrip toolStrip, MacroDictionary macros, EventHandler MacroButton_Click)
        {
            //look for a file named after this toolStrip item...
            var toolStripPathAndFile = Path.Combine(Common.Settings.UserFilePath, toolStrip.Name + ".menu");
            SimpleMenuItem simpleMenu;

            if (!File.Exists(toolStripPathAndFile))
            {
                return;
            }

            simpleMenu = (SimpleMenuItem)LoadObjectFromXMLFile(toolStripPathAndFile, typeof(SimpleMenuItem));

            BindMenuMacros(toolStrip.Items, simpleMenu, macros, MacroButton_Click);            
        }

        /// <summary>
        /// creates toolstrip items under toolstrip, and binds them to macros in our macro dictionary
        /// </summary>
        /// <param name="simpleMenuItems">The simple menu items.</param>
        /// <param name="macros">The macros.</param>
        /// <returns></returns>
        private static void BindMenuMacros(ToolStripItemCollection toolStripItems, SimpleMenuItem simpleMenuItem, MacroDictionary macros, EventHandler MacroButton_Click)
        {
            if (simpleMenuItem.Children != null)
            {
                foreach (var childItem in simpleMenuItem.Children)
                {

                    ToolStripItem newItem = childItem.ToToolStripItem(macros, MacroButton_Click);
                    if (newItem != null)
                    {
                        toolStripItems.Add(newItem);
                    }

                    if (newItem is ToolStripDropDownItem)
                    {
                        BindMenuMacros((newItem as ToolStripDropDownItem).DropDownItems, childItem, macros, MacroButton_Click);
                    }
                    else if (newItem is ToolStripSplitButton)
                    {
                        BindMenuMacros((newItem as ToolStripSplitButton).DropDownItems, childItem, macros, MacroButton_Click);
                    }
                }
            }
        }

        internal static void SaveMenu(ToolStrip toolStrip)
        {
            EnsurePathExists(Common.Settings.UserFilePath);
            var toolStripPathAndFile = Path.Combine(Common.Settings.UserFilePath, toolStrip.Name + ".menu");
            var simpleMenuItem = new SimpleMenuItem(toolStrip);

            SaveObjectToXmlFile(toolStripPathAndFile, simpleMenuItem);            
        }
    }

    public enum ApplicationEvents
    {
        Logging,
        Logged,
        ShowingTodo,
        ShowedTodo,
        SavingSettings,
        SavedSettings
    }
}